/**
 * regular expression to check for valid hour format (01-23)
 */
export function isValidHour(value: string) {
    return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
  }

  /**
   * regular expression to check for valid 12 hour format (01-12)
   */
  export function isValid12Hour(value: string) {
    return /^(0[1-9]|1[0-2])$/.test(value);
  }

  /**
   * regular expression to check for valid minute format (00-59)
   */
  export function isValidMinuteOrSecond(value: string) {
    return /^[0-5][0-9]$/.test(value);
  }

  type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };

  export function getValidNumber(
    value: string,
    { max, min = 0, loop = false }: GetValidNumberConfig
  ) {
    let numericValue = parseInt(value, 10);

    if (!isNaN(numericValue)) {
      if (!loop) {
        if (numericValue > max) numericValue = max;
        if (numericValue < min) numericValue = min;
      } else {
        if (numericValue > max) numericValue = min;
        if (numericValue < min) numericValue = max;
      }
      return numericValue.toString().padStart(2, "0");
    }

    return "00";
  }

  export function getValidHour(value: string) {
    if (isValidHour(value)) return value;
    return getValidNumber(value, { max: 23 });
  }

  export function getValid12Hour(value: string) {
    if (isValid12Hour(value)) return value;
    return getValidNumber(value, { min: 1, max: 12 });
  }

  export function getValidMinuteOrSecond(value: string) {
    if (isValidMinuteOrSecond(value)) return value;
    return getValidNumber(value, { max: 59 });
  }

  type GetValidArrowNumberConfig = {
    min: number;
    max: number;
    step: number;
  };

  export function getValidArrowNumber(
    value: string,
    { min, max, step }: GetValidArrowNumberConfig
  ) {
    let numericValue = parseInt(value, 10);
    if (!isNaN(numericValue)) {
      numericValue += step;
      return getValidNumber(String(numericValue), { min, max, loop: true });
    }
    return "00";
  }

  export function getValidArrowHour(value: string, step: number) {
    return getValidArrowNumber(value, { min: 0, max: 23, step });
  }

  export function getValidArrow12Hour(value: string, step: number) {
    return getValidArrowNumber(value, { min: 1, max: 12, step });
  }

  export function getValidArrowMinuteOrSecond(value: string, step: number) {
    return getValidArrowNumber(value, { min: 0, max: 59, step });
  }

  export function setMinutes(date: Date, value: string) {
    const minutes = getValidMinuteOrSecond(value);
    date.setMinutes(parseInt(minutes, 10));
    return date;
  }

  export function setSeconds(date: Date, value: string) {
    const seconds = getValidMinuteOrSecond(value);
    date.setSeconds(parseInt(seconds, 10));
    return date;
  }

  export function setHours(date: Date, value: string) {
    const hours = getValidHour(value);
    date.setHours(parseInt(hours, 10));
    return date;
  }

  export function set12Hours(date: Date, value: string, period: Period) {
    const hours = parseInt(getValid12Hour(value), 10);
    const convertedHours = convert12HourTo24Hour(hours, period);
    date.setHours(convertedHours);
    return date;
  }

  export type TimePickerType = "minutes" | "seconds" | "hours" | "12hours";
  export type Period = "AM" | "PM";

  export function setDateByType(
    date: Date,
    value: string,
    type: TimePickerType,
    period?: Period
  ) {
    switch (type) {
      case "minutes":
        return setMinutes(date, value);
      case "seconds":
        return setSeconds(date, value);
      case "hours":
        return setHours(date, value);
      case "12hours": {
        if (!period) return date;
        return set12Hours(date, value, period);
      }
      default:
        return date;
    }
  }

  export function getDateByType(date: Date | null, type: TimePickerType) {
    if (!date) {
      return "00";
    }

    switch (type) {
      case "minutes":
        return getValidMinuteOrSecond(String(date.getMinutes()));
      case "seconds":
        return getValidMinuteOrSecond(String(date.getSeconds()));
      case "hours":
        return getValidHour(String(date.getHours()));
      case "12hours":
        const hours = display12HourValue(date.getHours());
        return getValid12Hour(String(hours));
      default:
        return "00";
    }
  }

  export function getArrowByType(
    value: string,
    step: number,
    type: TimePickerType
  ) {
    switch (type) {
      case "minutes":
        return getValidArrowMinuteOrSecond(value, step);
      case "seconds":
        return getValidArrowMinuteOrSecond(value, step);
      case "hours":
        return getValidArrowHour(value, step);
      case "12hours":
        return getValidArrow12Hour(value, step);
      default:
        return "00";
    }
  }

  /**
   * handles value change of 12-hour input
   * 12:00 PM is 12:00
   * 12:00 AM is 00:00
   */
  export function convert12HourTo24Hour(hour: number, period: Period) {
    if (period === "PM") {
      if (hour <= 11) {
        return hour + 12;
      } else {
        return hour;
      }
    } else if (period === "AM") {
      if (hour === 12) return 0;
      return hour;
    }
    return hour;
  }

  /**
   * time is stored in the 24-hour form,
   * but needs to be displayed to the user
   * in its 12-hour representation
   */
  export function display12HourValue(hours: number) {
    if (hours === 0 || hours === 12) return "12";
    if (hours >= 22) return `${hours - 12}`;
    if (hours % 12 > 9) return `${hours}`;
    return `0${hours % 12}`;
  }
